using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;

namespace Meta
{
	[Serializable]
	public abstract class CompiledProcedure : UserProcedure
	{
		public CompiledProcedure() {}
		public override object Apply(object[] args) 
		{
			return Apply(args, EvalStack.CurrentThreadEvalStack);
		}

		public CompiledProcedure(Environment parentEnvironment) 
		{
			this.parentEnvironment = parentEnvironment;
		}

		public CompiledProcedure(Environment parentEnvironment, string documentation) : this(parentEnvironment) 
		{
			this.documentation = documentation;
		}

		public Environment MakeEnvironment(object[] arglist, int args) 
		{
			if (arglist.Length!=args)
				throw new ArgumentCountException(arglist, ((IArglist)this).Arglist);
			return new Environment(this, arglist, parentEnvironment, 0);
		}

		public Environment MakeRestArgEnvironment(object[] arglist, int args) 
		{
			if (arglist.Length<args-1)
				throw new ArgumentCountException(arglist, ((IArglist)this).Arglist);
			object[] realArgs = new Object[args];
			object[] restArg = new Object[arglist.Length-(args-1)];
			Array.Copy(arglist, 0, realArgs, 0, args-1);
			Array.Copy(arglist, args-1, restArg, 0, restArg.Length);
			realArgs[realArgs.Length-1] = restArg;
			return new Environment(this, realArgs, parentEnvironment, 0);
		}

		public Environment ParseArgumentList(object[] arglist, int args, int requiredArgs, int optionalArgs, int keyStart, bool hasRest,
			                                  object[] keyNames) 
		{
			// Check argument count
			if ((arglist.Length<requiredArgs)||(keyStart<0 && !hasRest && arglist.Length>(requiredArgs+optionalArgs)))
				throw new ArgumentCountException(arglist, ((IArglist)this).Arglist);

			// Alocate the data vector for the environment
			object[] realArgs = new Object[args];

			// Mark arguments missing so the procedure can default them later
			for (int i=0; i<args; i++)
				realArgs[i] = System.Reflection.Missing.Value;

			// Copy over the positional (required+optional) arguments
			int positionalArgs = Math.Min(arglist.Length, requiredArgs+optionalArgs);
			Array.Copy(arglist, 0, realArgs, 0, positionalArgs);

			// Handle keyword arguments
			if (keyStart>=0) 
			{
				if ((arglist.Length-positionalArgs)%2!=0)
					throw new ArgumentException("Odd number of keyword arguments passed; must be even");
				for (int j=positionalArgs; j<arglist.Length-1; j+=2)
					if (!(arglist[j] is Keyword))
						throw new ArgumentTypeException("keyword name", arglist[j], typeof(Keyword));
				for (int key=0; key<keyNames.Length; key++) 
				{
					object keyname = keyNames[key];
					// Search for keyword arg
					for (int k=positionalArgs; k<arglist.Length-1; k+=2) 
					{
						if (arglist[k]==keyname)
							// found it
							realArgs[keyStart+key] = arglist[k+1];
					}
				}
			}

			// Handle restArg, if any
			if (hasRest) 
			{
				object[] restArg = new Object[arglist.Length-(positionalArgs)];
				Array.Copy(arglist, positionalArgs, restArg, 0, restArg.Length);
				realArgs[realArgs.Length-1] = restArg;
			}
			return new Environment(this, realArgs, parentEnvironment, 0);
		}
	}

#if notdef
	public class ArglistTest : CompiledProcedure 
	{
		int argc;
		int requiredArgs;
		int optionalArgs;
		int keyStart;
		bool hasRest;
		object[] keyNames;
		public ArglistTest(int requiredArgs, int optionalArgs, object[] keyNames, bool hasRest) 
		{
			this.requiredArgs = requiredArgs;
			this.optionalArgs = optionalArgs;
			argc = requiredArgs+optionalArgs;

			this.keyNames = keyNames;
			if (keyNames!=null) 
			{
				keyStart = argc;
				argc += keyNames.Length;
			} else
				keyStart = -1;

			this.hasRest = hasRest;
			if (hasRest)
				argc++;
		}

		public override object Apply(object[] args, EvalStack estack)
		{
			return ParseArgumentList(args, argc, requiredArgs, optionalArgs, keyStart, hasRest, keyNames);
		}

	}
#endif

	// For use by the class compiler (see Packages.SimpleClasses) to mark that a class's
	// constructor calls the initialize generic procedure.
	[AttributeUsage(AttributeTargets.Class)]
	public class ConstructorCallsInitialize : Attribute 
	{
	}

	public class CompilerUtils 
	{
		public static object SetMember(object o, string fieldName, object newValue) 
		{
			return o.GetType().InvokeMember(fieldName, BindingFlags.SetField | BindingFlags.Instance | BindingFlags.Public, null, o, new object[] { newValue });
		}

#if USE_CUSTOMCONSTANTATTRIBUTE
		//
		// Find all CustomConstantAttributes for static fields and initialize the fields
		//
		public static void InitializeFields(Type t) 
		{
			foreach (FieldInfo f in t.GetFields(BindingFlags.Static|BindingFlags.Public|BindingFlags.NonPublic)) 
			{
				object[] attribs = f.GetCustomAttributes(typeof(CustomConstantAttribute), false);
				if (attribs.Length>0) 
				{
					CustomConstantAttribute a = (CustomConstantAttribute)attribs[0];
					f.SetValue(null, a.Value);
				}
			}
		}
#endif
	}

#if USE_CUSTOMCONSTANTATTRIBUTE
	[Serializable]
	[AttributeUsage(AttributeTargets.Field)]
	public class SymbolConstant : CustomConstantAttribute
	{
		Symbol val;
		public SymbolConstant(string pname) 
		{
			val = Symbol.Intern(pname);
		}

		public override object Value 
		{
			get 
			{
				return val;
			}
		}

		public static CustomAttributeBuilder Builder(Symbol s) 
		{
			ConstructorInfo c = typeof(SymbolConstant).GetConstructor(new Type[] { typeof(String) });
			return new CustomAttributeBuilder(c, new object[] { s.name });
		}
	}
#endif
}
